package com.google.code.shardbatis.converter;

import java.io.StringReader;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;

import com.google.code.shardbatis.ShardException;
import com.google.code.shardbatis.builder.ShardConfigHolder;
import com.google.code.shardbatis.strategy.ShardStrategy;
import com.google.code.shardbatis.util.FourTuples;
import com.google.code.shardbatis.util.MapperUtils;
import com.google.code.shardbatis.util.TwoTuples;

import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.parser.CCJSqlParserManager;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.delete.Delete;
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.update.Update;

public class SqlConverterFactory {
	private static final Log log = LogFactory.getLog(SqlConverterFactory.class);

	private static SqlConverterFactory factory = new SqlConverterFactory();
	private Map<String, SqlConverter> converterMap;
	private CCJSqlParserManager pm;

	public static SqlConverterFactory getInstance() {
		return factory;
	}

	private SqlConverterFactory() {
		this.converterMap = new HashMap<String, SqlConverter>();
		this.pm = new CCJSqlParserManager();
		register();
	}

	private void register() {
		this.converterMap.put(Select.class.getName(), new SelectSqlConverter());
		this.converterMap.put(Insert.class.getName(), new InsertSqlConverter());
		this.converterMap.put(Update.class.getName(), new UpdateSqlConverter());
		this.converterMap.put(Delete.class.getName(), new DeleteSqlConverter());
	}

	/**
	 * 
	 * @param sql
	 * @param params
	 * @param mapperId
	 * @return 
	 * 	List<SqlConverterOrderBy> -- 
	 * 	String[] 	--	转换后SQL数组
	 * 	String 		--	原SQL表名 
	 * 	String[]	--	转换后的表数组
	 * @throws ShardException
	 */
	public FourTuples<List<SqlConverterOrderBy>, String[], String, String[]> convert(String sql, Object params, String mapperId) throws ShardException {
		TwoTuples<String, String[]> ttTableNames = getTableNames(sql, params, mapperId);
		try {
			List<SqlConverterOrderBy> orderBys = null;
			if (null != ttTableNames && null != ttTableNames.b) {
				
				Statement statement = this.pm.parse(new StringReader(sql));
				SqlConverter converter = (SqlConverter) this.converterMap.get(statement.getClass().getName());
				orderBys = converter.getOrderBys(statement);
				
				String[] sqls = new String[ttTableNames.b.length];
				for (int i = 0; i < ttTableNames.b.length; i++) {
					if (null == converter || null == ttTableNames.b[i]) { continue; }
					sqls[i] = converter.convert(statement, ttTableNames.b[i]);
				}
				
				return new FourTuples<List<SqlConverterOrderBy>, String[], String, String[]>(
					orderBys, 
					sqls, 
					ttTableNames.a,
					ttTableNames.b
				);
			}
			else {
				return new FourTuples<List<SqlConverterOrderBy>, String[], String, String[]>(
					orderBys, 
					new String[] { sql }, 
					ttTableNames.a,
					ttTableNames.b
				);
			}
		}
		catch (JSQLParserException e) {
			log.error(e.getMessage(), e);
			throw new ShardException(e);
		}
	}
	
	private TwoTuples<String, String[]> getTableNames(String sql, Object params, String mapperId) throws ShardException {
		Statement statement = null;
		try {
			statement = this.pm.parse(new StringReader(sql));
			
			SqlConverter converter = (SqlConverter) this.converterMap.get(statement.getClass().getName());
			
			String originalTableName = converter.getOriginalTableName(statement);
			
			ShardConfigHolder configFactory = ShardConfigHolder.getInstance();
			
//			String tableName = originalTableName.replace("`", "");
//			tableName = tableName.replace("\"", "");
			String tableName = MapperUtils.convertName(originalTableName);
			ShardStrategy strategy = configFactory.getStrategy(tableName);
			
			if (null == strategy) {
				return new TwoTuples<String, String[]>(
					tableName, 
					new String[] { tableName }
				);
			}
			return new TwoTuples<String, String[]>(
				tableName, 
				strategy.getTargetTableNames(tableName, params, mapperId)
			);
		}
		catch (JSQLParserException e) {
			log.error(e.getMessage(), e);
			throw new ShardException(e);
		}
	}
	
}